package worker

import (
	"context"
	"fmt"
	"gitee.com/zhengjianfeng/crontab-worker/common"
	"time"
)

var (
	// 单例
	G_scheduler *Scheduler
)

// 调度任务
type Scheduler struct {
	workerJobEventChan      chan *common.WorkerJobEvent               // etcd任务事件队列
	workerJobPlanTable      map[string]*common.WorkerJobSchedulerPlan // 任务调整计划表
	workerJobExecutingTable map[string]*common.WorkerJobExecuteInfo   // 任务执行表
	workerJobResultChan     chan *common.WorkerJobExecuteResult       // 任务结果队列
}

// 推送任务变化事件
func (scheduler *Scheduler) PushWorkerJobEvent(workerJobEvent *common.WorkerJobEvent) {
	//fmt.Println("推送给调度器: ", workerJobEvent)
	scheduler.workerJobEventChan <- workerJobEvent
}

// 调度协程
func (scheduler *Scheduler) schedulerLoop() {
	var (
		workerJobEvent  *common.WorkerJobEvent
		schedulerTimer  *time.Timer
		schedulerAfter  time.Duration
		workerJobResult *common.WorkerJobExecuteResult
	)

	// 初始化(默认1秒)
	schedulerAfter = scheduler.TrSchedule()

	//调度的延时计时器
	schedulerTimer = time.NewTimer(schedulerAfter)

	// 定时任务common.WorkerJob
	for {
		// 检测所有的任务
		select {
		case workerJobEvent = <-scheduler.workerJobEventChan: // 监听任务变化事件
			// 对我们的维护任务列表增删改查
			scheduler.handleWorkerJobEvent(workerJobEvent)
		case <-schedulerTimer.C: // 最近的任务到期了
		case workerJobResult = <-scheduler.workerJobResultChan: // 监听任务执行结果
			scheduler.handleWorkerJobResult(workerJobResult)
		}

		// 调度一次任务
		scheduler.TrSchedule()

		// 重置时间间隔
		schedulerTimer.Reset(schedulerAfter)
	}
}

// 尝试开始执行任务
func (scheduler *Scheduler) TyrStartWorkerJob(workerJobPlan *common.WorkerJobSchedulerPlan) {
	// 执行和调度是两个事情

	var (
		workerJobExecuteInfo *common.WorkerJobExecuteInfo
		workerJobExecuting   bool
	)

	// 执行的任务可能运行很久 1分钟会执行60次但是只能执行一次，防止并发

	// 如果任务正在执行，跳过本次调度
	if workerJobExecuteInfo, workerJobExecuting = scheduler.workerJobExecutingTable[workerJobPlan.JobBatch]; workerJobExecuting {
		fmt.Println("任务尚未退出，跳过执行: ", workerJobPlan.WorkerJobs)
		return
	}

	// 构建执行状态信息
	workerJobExecuteInfo = common.BuildWorkerJobExecuteInfo(workerJobPlan)

	// 保存执行状态
	scheduler.workerJobExecutingTable[workerJobPlan.JobBatch] = workerJobExecuteInfo

	//执行任务 TODO：执行任务
	G_executor.ExecuteWorkerJob(workerJobExecuteInfo, workerJobPlan)

}

// 重新计算任务调度状态
func (scheduler *Scheduler) TrSchedule() (schedulerAfter time.Duration) {

	var (
		now           time.Time // 当前时间
		workerJobPlan *common.WorkerJobSchedulerPlan
		nearTime      *time.Time
	)

	// 如果任务表为空的话，随便睡眠多久
	if len(scheduler.workerJobPlanTable) == 0 {
		schedulerAfter = 1 * time.Second
		return
	}

	// 当前时间
	now = time.Now()

	// 遍历所有任务
	for _, workerJobPlan = range scheduler.workerJobPlanTable {
		if workerJobPlan.NexTIme.Before(now) || workerJobPlan.NexTIme.Equal(now) {
			workerJobPlan.RealRunTotalNum += 1 // 执行次数+1

			// 尝试执行任务
			scheduler.TyrStartWorkerJob(workerJobPlan)
			workerJobPlan.NexTIme = workerJobPlan.Expr.Next(now) // 下次执行时间
		}

		// 统计最近一个要过期的任务时间
		if nearTime == nil || workerJobPlan.NexTIme.Before(*nearTime) {
			nearTime = &workerJobPlan.NexTIme
		}
	}

	// 下次调度间隔（最近要执行的任务时间 -  当前时间）
	schedulerAfter = (*nearTime).Sub(now)

	return
}

// 处理任务事件
func (scheduler *Scheduler) handleWorkerJobEvent(workerJobEvent *common.WorkerJobEvent) {

	var (
		err                    error
		workerJobSchedulerPlan *common.WorkerJobSchedulerPlan
		//workerJobExisted       bool
		//mutex sync.Mutex
	)
	switch workerJobEvent.EventType {
	case common.WORKER_JOB_EVENT_SAVE:

		// 先检查一下任务是否已经存在了
		// cookie一样的合并在一起
		if scheduler.workerJobPlanTable[workerJobEvent.WorkerJob.JobBatch] != nil {
			// 更新该批次下任务计划执行总次数再减去已经完成的次数
			//mutex.Unlock()
			//mutex.Unlock()

			scheduler.workerJobPlanTable[workerJobEvent.WorkerJob.JobBatch].PlanRunToTalNum = workerJobEvent.WorkerJob.PlanRunNum + scheduler.workerJobPlanTable[workerJobEvent.WorkerJob.JobBatch].RealRunTotalNum

		} else {
			// 构造执行计划
			if workerJobSchedulerPlan, err = common.BuildWorkerJobSchedulerPlan(workerJobEvent.WorkerJob); err != nil {
				return
			}
			// 把该批次的任务加入到任务表中
			scheduler.workerJobPlanTable[workerJobEvent.WorkerJob.JobBatch] = workerJobSchedulerPlan
		}

		// 把任务加入到对应批次的任务表中
		scheduler.workerJobPlanTable[workerJobEvent.WorkerJob.JobBatch].WorkerJobs[workerJobEvent.WorkerJob.Name] = workerJobEvent.WorkerJob

	case common.WORKER_JOB_EVENT_DELETE:
		// 删除事件 暂时不做处理
		fmt.Println("删除事件 暂时不做处理")
		/*if workerJobSchedulerPlan, workerJobExisted = scheduler.workerJobPlanTable[workerJobEvent.WorkerJob.Name]; workerJobExisted {
			delete(scheduler.workerJobPlanTable, workerJobEvent.WorkerJob.Name)
		}*/
	}
}

// 处理任务执行结果
func (scheduler *Scheduler) handleWorkerJobResult(result *common.WorkerJobExecuteResult) {

	var (
		workerJobExisted bool
		workerJob        *common.WorkerJob
		err              error
		workerJobKey     string
	)

	if result.Err != common.ERR_LOCK_ALREADY_REQUIRED {
		fmt.Println("任务批次: ", result.JobBatch, " 计划完成次数", result.WorkerJobExecuteInfo.PlanRunTotalNum, " 第 ", result.WorkerJobExecuteInfo.BatchRunNum, " 次任务执行完成： output:", string(result.Output))
	}

	// 删除执行状态
	delete(scheduler.workerJobExecutingTable, result.JobBatch)

	//  如果计划执行次数小于等于实际执行次数就删除任务，否则继续任务 workerJobPlan.RunTotalNum
	// 执行次数等于0代表一直执行 计划执行次数不等于0 而且 实际执行次数大于计划执行次数
	if result.WorkerJobExecuteInfo.PlanRunTotalNum != 0 && result.WorkerJobExecuteInfo.BatchRunNum >= result.WorkerJobExecuteInfo.PlanRunTotalNum {
		if _, workerJobExisted = scheduler.workerJobPlanTable[result.JobBatch]; workerJobExisted {
			// 删除任务表里面的该任务
			delete(scheduler.workerJobPlanTable, result.JobBatch)

			// 同时删除etcd中的任务
			for _, workerJob = range result.WorkerJobSchedulerPlan.WorkerJobs {
				workerJobKey = common.WORKER_JOB_SAVE_DIR + workerJob.Name
				if _, err = G_workerJobMgr.kv.Delete(context.TODO(), workerJobKey); err != nil {
					fmt.Println("删除成功")
					// TODO:日志一下错误原因
				}
			}

			// TODO: 发通知 回复完成任务
		}
	}

	// 生成执行日志
	// fmt.Println("生成执行日志:", result)
}

// 初始化调度器
func InitScheduler() (err error) {

	G_scheduler = &Scheduler{
		workerJobEventChan:      make(chan *common.WorkerJobEvent, 1000),
		workerJobPlanTable:      make(map[string]*common.WorkerJobSchedulerPlan),
		workerJobExecutingTable: make(map[string]*common.WorkerJobExecuteInfo),
		workerJobResultChan:     make(chan *common.WorkerJobExecuteResult, 1000),
	}

	// 启动调度协程
	go G_scheduler.schedulerLoop()

	return
}

// 回传任务执行结果
func (scheduler *Scheduler) PushJobResult(workerJobResult *common.WorkerJobExecuteResult) {
	scheduler.workerJobResultChan <- workerJobResult
}
